package com.dbflow5.adapter.saveable;

import com.dbflow5.adapter.ModelAdapter;
import com.dbflow5.database.DatabaseStatement;
import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.runtime.NotifyDistributor;
import com.dbflow5.structure.ChangeAction;

/**
 * Description: Defines how models get saved into the DB. It will bind values to [DatabaseStatement]
 * for all CRUD operations as they are wildly faster and more efficient than ContentValues.
 */
public class ModelSaver<T> {
    public static final int INSERT_FAILED = -1;

    public ModelAdapter<T> modelAdapter;

    public synchronized boolean save(T model, DatabaseWrapper wrapper) {
        DatabaseStatement insertStatement = modelAdapter.getSaveStatement(wrapper);
        return save(model, insertStatement, wrapper);
    }

    public synchronized boolean save(T model, DatabaseStatement insertStatement, DatabaseWrapper wrapper) {
        modelAdapter.saveForeignKeys(model, wrapper);
        boolean success = false;
        if(insertStatement != null) {
            modelAdapter.bindToInsertStatement(insertStatement, model);
            long id = insertStatement.executeInsert();
            success = id > INSERT_FAILED;
            if (success) {
                modelAdapter.updateAutoIncrement(model, id);
                new NotifyDistributor().notifyModelChanged(model, modelAdapter, ChangeAction.CHANGE);
            }
        }
        return success;
    }

    public synchronized boolean update(T model, DatabaseWrapper wrapper) {
        DatabaseStatement updateStatement = modelAdapter.getUpdateStatement(wrapper);
        return update(model, updateStatement, wrapper);
    }

    public synchronized boolean update(T model, DatabaseStatement databaseStatement, DatabaseWrapper wrapper) {
        modelAdapter.saveForeignKeys(model, wrapper);
        modelAdapter.bindToUpdateStatement(databaseStatement, model);
        boolean successful = databaseStatement.executeUpdateDelete() != 0L;
        if (successful) {
            new NotifyDistributor().notifyModelChanged(model, modelAdapter, ChangeAction.UPDATE);
        }
        return successful;
    }

    public synchronized long insert(T model, DatabaseWrapper wrapper) {
        DatabaseStatement insertStatement = modelAdapter.getInsertStatement(wrapper);
        return insert(model, insertStatement, wrapper);
    }

    public synchronized long insert(T model, DatabaseStatement insertStatement, DatabaseWrapper wrapper) {
        modelAdapter.saveForeignKeys(model, wrapper);
        long id = INSERT_FAILED;
        if(insertStatement != null){
            modelAdapter.bindToInsertStatement(insertStatement, model);
            id = insertStatement.executeInsert();
            if (id > INSERT_FAILED) {
                modelAdapter.updateAutoIncrement(model, id);
                new NotifyDistributor().notifyModelChanged(model, modelAdapter, ChangeAction.INSERT);
            }
        }
        return id;
    }

    public synchronized boolean delete(T model, DatabaseWrapper wrapper) {
        DatabaseStatement deleteStatement = modelAdapter.getDeleteStatement(wrapper);
        return delete(model, deleteStatement, wrapper);
    }

    public synchronized boolean delete(T model, DatabaseStatement deleteStatement, DatabaseWrapper wrapper) {
        modelAdapter.deleteForeignKeys(model, wrapper);
        modelAdapter.bindToDeleteStatement(deleteStatement, model);

        boolean success = deleteStatement.executeUpdateDelete() != 0L;
        if (success) {
            new NotifyDistributor().notifyModelChanged(model, modelAdapter, ChangeAction.DELETE);
        }
        modelAdapter.updateAutoIncrement(model, 0);
        return success;
    }
}

